/**********************************************************************\
 © COPYRIGHT 2006 Corporation for National Research Initiatives (CNRI);
                        All rights reserved.
               
        The HANDLE.NET software is made available subject to the
         Handle System Public License, which may be obtained at
         http://hdl.handle.net/4263537/5030 or hdl:4263537/5030
\**********************************************************************/

package net.handle.apps.servlet_proxy;

import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;

import net.handle.hdllib.*;

public class XRefOpenURL
  extends HttpServlet
{
  static String optInFile;
  static String defaultPage = null;
  private HashMap optIn = null;
  private long lastModified = 0;
  
  private HandleResolver resolver;
  private Properties config;
  private RotatingAccessLog logger;
  private boolean logReferrer = false;
  private boolean logHSAdmin = false;
  
  // cookie-based local resolver support
  boolean sfxEnabled;
  String sfxCookieName;    
  
  static final String URL_TYPE[] = {"URL"};
  
  private void loadOptIn() throws ServletException {
    String file;
    if (optInFile == null) {
      throw new ServletException("Error loading crossref opt-in file!");
    }
    try {
      File f = new File(optInFile);
      if (lastModified == f.lastModified()) return;
      if (optIn == null) optIn = new HashMap();
      else optIn.clear();
      System.err.println("reloading "+optInFile);
      lastModified = f.lastModified();
      InputStream in = new FileInputStream(f);
      byte b[] = new byte[(int)f.length()];
      in.read(b);
      file = new String(b);
    } catch (IOException e){
      return;
    } 
    
    StringTokenizer st = new StringTokenizer(file, "\n");
    while (st.hasMoreTokens()){
      String line = st.nextToken();
      if (line.indexOf(" ") == -1){
        // no rewrite
        optIn.put(line, null);
        System.err.println("OPT IN, NO REWRITE: "+line);
      } else {
        String s[] = line.split(" ");
        System.err.println("OPT IN: "+s[0]+" REWRITE "+s[1]);
        optIn.put(s[0], s[1]);
      }
    }
  }

  private boolean loadedSettings = false;
  private synchronized void loadSettings()
    throws ServletException
  {
    if(loadedSettings) return;
    
    System.err.println("initializing xref servlet");
    resolver = new HandleResolver();
    
    // load configuration properties
    config = new Properties();
    try {
      config.load(getClass().getResourceAsStream("hdlproxy.properties"));
    } catch (IOException e){
      throw new ServletException("Error loading servlet properties: "+e);
    }

    String conffile = getServletConfig().getInitParameter("config");
    System.err.println("load xref properties from "+conffile);
    if (conffile != null)  {
      try {
        config.load(new FileInputStream(conffile));
      } catch (Exception e) { 
        throw new ServletException("Error loading "+conffile);
      }
    }
    
    // get the singleton logger so that we don't clobber the logs from other servlets
    System.err.println("loading logger for openurl proxy");    
    try {
      String logFileName = config.getProperty("access_log");
      if(logFileName!=null) {
        File logDir = new File(logFileName).getParentFile();
        logger = RotatingAccessLog.getMonthlyLogger(logDir);
      }
    } catch (Exception e) {
      throw new ServletException("Error loading logger: "+e);
    }
    
    
    sfxCookieName = config.getProperty("sfx_cookie", null);
    sfxEnabled = sfxCookieName!=null;
    logReferrer = Boolean.valueOf(config.getProperty("log_referrer","false")).booleanValue();
    logHSAdmin = Boolean.valueOf(config.getProperty("log_hs_admin","false")).booleanValue();
    
    System.err.println("loading opt-in file: "+
                       getServletConfig().getInitParameter("optInFile"));
    optInFile = getServletConfig().getInitParameter("optInFile");
    
    loadedSettings = true;
  }
  
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  {
    loadSettings();
    long intime = System.currentTimeMillis();
    
    String query = request.getQueryString();

    PrintWriter out = response.getWriter();

    if (query == null || query.trim().length() == 0){
      // no request, show default page
      response.sendRedirect("/");
      return;
    }

    String doi = null; 
    String rfr_dat = null;
    String urlVersion = "";
    boolean debug = false;
    boolean ignoreLocalResolver = false;
      
    // params is the string to append to the target url
    StringBuffer params = new StringBuffer();
      
    response.setContentType("text/plain");

    // extract the OpenURL parameters
    int strLen = query.length();
    int idx = 0;
    for(int i=0; i<=strLen; i++) {
      if(i==strLen || query.charAt(i)=='&') {
        String segment = query.substring(idx, i);
        idx = i+1;

        int eqIdx = segment.indexOf('=');
        String key = eqIdx<0 ? segment : segment.substring(0, eqIdx);
        String val = eqIdx<0 ? "" : segment.substring(eqIdx+1);
          
        if (key.equals("rft_id") || key.equals("id")){
          if (val.toLowerCase().startsWith("ori:doi:")){
            doi = val.substring("ori:doi:".length());
          } else if (val.toLowerCase().startsWith("info:doi/")){
            doi = val.substring("info:doi/".length());
          } else if (val.toLowerCase().startsWith("doi:")){
            doi = val.substring("doi:".length());
          } else if (val.toLowerCase().startsWith("hdl:")){
            doi = val.substring("hdl:".length());
          } else {
            doi = val;
          }
        } else if (key.equals("rfr_dat")){
          rfr_dat = val;
        } else if (key.equals("debug")){
          debug = true;
        } else if(key.equals("nosfx") || key.equals("nols")) {
          ignoreLocalResolver = true;
        } else if(key.equals("url_ver")) {
          urlVersion = val;
        }
          
        if (!key.equals("rft_dat")){
          // don't include rft_dat in the param list
          if (params.length() != 0){
            params.append("&");
          }
          params.append(key);
          params.append("=");
          params.append(val);
        }
      }
    }
      
    if (doi == null) {
      logAccess("HTTP:OpenURL", AbstractResponse.OC_RESOLUTION,
                AbstractResponse.RC_ERROR, request,
                "", null, request.getHeader("Referer"),
                System.currentTimeMillis() - intime);
      error("No DOI specified("+query+").", response);
      return;
    }

    doi = URLDecoder.decode(doi, "UTF-8");
    // System.err.println("CROSSREF PROXY: resolving "+doi);

    // if the request came with an SFX/local resolver cookie, obey it before anything else
    if(sfxEnabled && !ignoreLocalResolver) {
      Cookie cookies[] = request.getCookies();
      for (int i=0; cookies!=null && i<cookies.length; i++) {
        Cookie cookie = cookies[i];
        if (cookie!=null && cookie.getName().equals(sfxCookieName)){
          String sfxBaseURL = cookie.getValue();
          if (sfxBaseURL!=null) {
            if (sfxBaseURL.startsWith("\"")) sfxBaseURL=sfxBaseURL.substring(1);
            if (sfxBaseURL.endsWith("\"")) {
              sfxBaseURL = sfxBaseURL.substring(0, sfxBaseURL.length()-1);
            }
            logAccess("HTTP:SFX", AbstractResponse.OC_RESOLUTION, 0, request,
                      doi, null, request.getHeader("Referer"),
                      System.currentTimeMillis() - intime);
            
            response.sendRedirect(sfxBaseURL+"?id=doi:"+URLEncoder.encode(doi));
            return;
          }
        }
      }
    } // end SFX/local resolver checking
    
    HandleValue vals[];
    try {
      vals = resolver.resolveHandle(doi, URL_TYPE, null);
    } catch (Exception e){
      logAccess("HTTP:OpenURL", AbstractResponse.OC_RESOLUTION,
                AbstractResponse.RC_ERROR, request,
                doi, null, request.getHeader("Referer"),
                System.currentTimeMillis() - intime);
      error("Error resolving doi:'"+doi+"': "+e.getMessage(), response);
      return;
    }

    String urlstr = null;
    for(int i=0; vals!=null && i<vals.length; i++) {
      if(vals[i].hasType(Common.STD_TYPE_URL)) {
        urlstr = vals[i].getDataAsString();
        break;
      }
    }
    if(urlstr==null) {
      logAccess("HTTP:OpenURL", AbstractResponse.OC_RESOLUTION,
                AbstractResponse.RC_VALUES_NOT_FOUND, request,
                doi, null, request.getHeader("Referer"),
                System.currentTimeMillis() - intime);
      error("No URL found in DOI:"+doi, response);
      return;
    }

    if(urlVersion.equals("Z39.88-2003")) {
      // this is a parameter-passing OpenURL - insert the parameters from the initial
      // OpenURL into the destination (aka referrent) URL.
      // this also maps some of the opted-in domains into 
      
      URL url = new URL(urlstr);
      String host = url.getHost();
      loadOptIn();
      
      boolean useParams = optIn.containsKey(host);
      String rewrite = (String)optIn.get(host);
      
      if (debug){
        out.println("DOI: "+doi);
        out.println("URL: "+url);
        out.println("Host: "+host);
        out.println("Use params?: "+useParams);
        if (useParams){
          out.println("Host Rewrite: "+rewrite);
        }
        out.println("Params: "+params);
        out.println("URL+PARAMS: "+url+"?"+query);
      }
      
      if (!useParams) {
        // redirect to registered url, no changes because the target domain wasn't registered
        logAccess("HTTP:OpenURL", AbstractResponse.OC_RESOLUTION, 0, request,
                  doi, null, request.getHeader("Referer"),
                  System.currentTimeMillis() - intime);
        response.sendRedirect(urlstr);
        return;
      }
      
      // construct outbound openurl
      if (rewrite != null) {
        urlstr = urlstr.replaceFirst(host, rewrite);
        url = new URL(urlstr);
      }
      
      String rft_dat = "";
      if (urlstr.toLowerCase().indexOf("url_ver=") >= 0){
        // registered url is already an OpenURL, extract rft_dat
        int i = urlstr.toLowerCase().indexOf("rft_dat=");
        int j = urlstr.toLowerCase().indexOf("&");
        if (j == -1) j = urlstr.length();
        if (i >= 0){
          rft_dat = urlstr.substring(i, j);
        }
      } else {
        // destination URL not an OpenURL -> stuff destination query string into rft_dat
        // and use the passed-in query string as the parameters
        String queryStr = url.getQuery();
        if(queryStr!=null) 
          rft_dat = URLEncoder.encode(queryStr, "UTF-8");
      }
      StringBuffer target = new StringBuffer();
      target.append(url.getProtocol());
      target.append("://");
      target.append(url.getAuthority());
      target.append(url.getPath());
      target.append("?");
      target.append(params);
      if (rft_dat.length() > 0) {
        if (params.length() > 0) {
          target.append("&");
        }
        target.append("rft_dat=");
        target.append(rft_dat);
      }
      String fragment = url.getRef();
      if (fragment != null && fragment.length() > 0){
        target.append("#");
        target.append(fragment);
      }
      if(debug) {
        out.println("Redirect To:: "+target.toString());
      } else {
        logAccess("HTTP:OpenURLPP", AbstractResponse.OC_RESOLUTION, 0, request,
                  doi, null, request.getHeader("Referer"),
                  System.currentTimeMillis() - intime);
        response.sendRedirect(target.toString());
      }
    } else {
      // this is a normal OpenURL resolution - no parameter passing
      if(debug) {
        out.println("Redirect To: (normal openurl) "+urlstr);
      } else {
        logAccess("HTTP:OpenURL", AbstractResponse.OC_RESOLUTION, 0, request,
                  doi, null, request.getHeader("Referer"),
                  System.currentTimeMillis() - intime);
        response.sendRedirect(urlstr);
      }
    }
  }

  public void error(String msg, HttpServletResponse response)
    throws IOException, ServletException 
  {
    System.err.println("CRPARAM ERROR: " + msg);
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<html><head><title>CrossRef Proxy Error</title></head><body>");
    out.println("<h3>Error:</h3> "+msg);
    out.println("</body></html>");
  }
  
  public synchronized void logAccess(String accessType, int oc, int rc,
                                     HttpServletRequest req, String hdl,
                                     HandleValue vals[], String referrer, long time)
  {
    if (logger == null) return;
    String addr = "";
    try { addr = req.getRemoteAddr(); } catch (Throwable t) {}
    
    StringBuffer msg = new StringBuffer(50);
    msg.append(addr==null ? "" : addr);
    msg.append(' ');
    msg.append(accessType);
    msg.append(" \"");
    msg.append(new Date());
    msg.append("\" ");
    msg.append(oc);
    msg.append(' ');
    msg.append(rc);
    msg.append(' ');
    msg.append(time);
    msg.append("ms ");
    msg.append(hdl);
    
    if (logHSAdmin){
      msg.append(" \"");
      boolean firstAdmin = true;
      for (int i=0; vals!=null && i<vals.length; i++){
        if(!vals[i].hasType(Common.STD_TYPE_HSADMIN)) {
          continue;
        }
        AdminRecord adm = new AdminRecord();
        try {
          Encoder.decodeAdminRecord(vals[i].getData(), 0, adm);
        }
        catch (HandleException e){
          continue;
        }
        if (!firstAdmin) msg.append(',');
        firstAdmin = false;
        
        msg.append(adm.adminIdIndex);
        msg.append(':');
        encodeLogField(msg, Util.decodeString(adm.adminId));
      }
      msg.append('"');
    }
    
    // log the referrer
    if(logReferrer) {
      msg.append(" \"");
      encodeLogField(msg, referrer);
      msg.append('"');
    }
    
    logger.logAccess(msg.toString());
  }
  
  private static final void encodeLogField(StringBuffer sb, String str) {
    if(str==null) return;
    int strLen = str.length();
    char ch;
    for(int i=0; i<strLen; i++) {
      ch = str.charAt(i);
      if(ch=='\\')
        sb.append("\\\\");
      else
        sb.append(ch);
    }
  }
  
}



